import React from 'react';
import {Progress, message} from 'antd';
import {Icon, ImgPreview} from '../index';
import {Tool, ToolRequest} from '../../utils';

import './index.scss';

//默认的上传地址
const action = 'http://www.mocky.io/v2/5cc8019d300000980a055e76';
//默认的文件类型
const fileTypeDefault = {types: ['image/jpeg', 'image/png'], msg: '只能上传JPG/JPEG/PNG格式的文件'};
//默认的文件大小
const fileSizeDefault = {
  max: {size: 1024 * 1024 * 1, msg: '文件大小不能超过1M'},
  min: {size: 1, msg: '文件大小需大于0KB'},
  autoCompress: true
};
//图片上传的状态，done表示上传成功，error表示上传失败，uploading表示正在上传
const statusList = {done: 'done', error: 'error', uploading: 'uploading'};

/**
 * 文件上传：基于antd的Upload实现，增加默认的自定义上传、文件数量限制
 * 参考：https://ant.design/components/upload-cn
 * 属性：
 * className：类名。
 * action：上传地址。类型：string。默认值：见url。
 * data：请求参数。类型：Object。
 * headers：请求头。类型：Object。
 * multiple：是否支持文件多选。类型：Boolean。默认值：false。
 * fileType：允许上传的文件类型。类型：Array。默认值：见fileTypeDefault
 * fileNum：最大上传文件数量。类型：number。默认值：1。
 * fileSize：最大上传文件大小。类型：number。默认值：见fileSizeDefault
 * fileList: 已经上传的文件列表。类型：Array。
 * fileListDefault: 默认上传的文件列表。类型：Array。
 * buttonText：上传问你文本。类型：String。默认值：上传。
 * uploadHandles: 手动上传事件。类型：Array
 * onUploadRequest：自定义请求。类型：Function({file})。
 * onStart：上传开始的回调。类型：Function()。
 * onSuccess：上传成功的回调。类型：Function()。
 * onError：上传失败的回调。类型：Function()。
 * onProgress：上传进度改变的回调。类型：Function()。
 * onPreview：预览图片的回调。类型：Function()。
 * onRemove：删除图片的回调。类型：Function()。
 * 注意：使用自定义请求时，如果有错误需要抛出。
 */
export default class Upload extends React.Component {
  static defaultProps = {
    className: '',
    action,
    multiple: false,
    fileType: fileTypeDefault,
    fileSize: fileSizeDefault,
    fileNum: 1,
    buttonText: '上传'
  };

  constructor(props) {
    super(props);
    this.state = {
      fileList: props.fileListDefault || [], //已经上传的文件列表
      previewIndex: -1 //预览图片的索引
    };
  }

  render() {
    let {className, style} = this.props;
    return (
      <div className={`g-upload ${className}`} style={style}>
        {this.renderList()}
        {this.renderItemButton()}
        {this.renderPreview()}
      </div>
    );
  }

  /**
   * 加载上传的图片
   */
  renderList() {
    let {fileList = this.state.fileList} = this.props;
    let views = fileList.map(this.renderItem);
    return views;
  }

  /**
   * 加载上传的图片项
   * @param file 选择的图片
   * @param index 选择的图片索引
   */
  renderItem = (file, index) => {
    return (
      <span className={this.getItemClass(file)} key={index}>
        <img className="item-img" src={file.url} />
        {this.renderItemProcess(file, index)}
        {this.renderItemActions(file, index)}
      </span>
    );
  };

  /**
   * 加载上传的图片项：预览和删除
   * @param file 选择的图片
   * @param index 选择的图片索引
   */
  renderItemActions(file = {}, index) {
    let {percent} = file;
    if (percent && percent < 100) return;

    return (
      <div className="item-action">
        <span className="item-action-icon">
          <Icon type="eye-o" className="item-action-eye" onClick={() => this.onPreview(file, index)} />
          <Icon type="delete" className="item-action-delete" onClick={() => this.onRemove(file, index)} />
        </span>
      </div>
    );
  }

  /**
   * 加载上传的图片项：进度条
   * @param file 正在上传的图片
   * @param index 选择的图片索引
   */
  renderItemProcess(file = {}, index) {
    let {percent} = file;
    if (!percent || percent === 100) return;

    return (
      <div className="item-process">
        <Progress className="item-process-item" type="circle" percent={percent} width={50} />
      </div>
    );
  }

  /**
   * 加载上传按钮
   */
  renderItemButton() {
    let {buttonText, multiple, fileNum, fileType, fileList = this.state.fileList} = this.props;
    if (fileNum <= fileList.length) return;

    return (
      <span className="button">
        <input
          ref={(ref) => (this.input = ref)}
          style={{display: 'none'}}
          type="file"
          multiple={multiple}
          accept={fileType.types}
          onChange={this.onChangeUpload}
        />
        <div className="button-items" onClick={this.onClickUpload}>
          <Icon type="plus" className="button-plus" />
          <div>{buttonText}</div>
        </div>
      </span>
    );
  }

  /**
   * 图片预览
   */
  renderPreview() {
    let {previewIndex} = this.state;
    let {fileList = this.state.fileList} = this.props;
    return (
      <ImgPreview
        imgShow={previewIndex > -1}
        imgIndex={previewIndex}
        imgList={fileList}
        onClose={() => this.setState({previewIndex: -1})}
        onChange={(previewIndex) => this.setState({previewIndex})}
      />
    );
  }

  /**
   * 点击上传事件
   */
  onClickUpload = (e) => {
    this.input.click();
  };

  /**
   * 监听上传元素的改变
   */
  onChangeUpload = (e) => {
    const files = e.target.files; //用户选中的文件
    Array.from(files).forEach((file, index) => {
      this.onCheckFile(file).then(this.onTransformFile).then(this.onUpload);
    });
    e.target.value = ''; //清空input的value，防止选择相同的文化后，change事件不起作用
  };

  /**
   * 检查文件是否可以上传
   * @param file 用户选中的文件
   */
  onCheckFile = (file) => {
    if (!file || file.length == 0) return;

    return new Promise((resolve, reject) => {
      let checkOK = this.onCheckFileType(file);
      if (checkOK) {
        resolve(file);
      } else {
        reject('请选择文件!');
      }
    });
  };

  /**
   * 检查文件类型
   * @param file 文件
   */
  onCheckFileType = (file) => {
    let {fileType, fileSize} = this.props;
    if (fileType && fileType.types && fileType.types.findIndex((i) => i === file.type) < 0) {
      message.error(fileType.msg);
      return false;
    }

    if (!fileSize) return true;
    let {max, min, autoCompress} = fileSize;
    let checkMax = autoCompress || file.size <= max.size;
    if (!checkMax) message.error(max.msg);
    let checkMin = file.size >= min.size;
    if (!checkMin) message.error(min.msg);

    return checkMax && checkMin;
  };

  /**
   * 转换文件
   * @param file 用户选中的文件
   */
  onTransformFile = (file) => {
    if (!file) return file;

    //将File文件转为base64，用于预览
    return Tool.getDataFromFile(file).then((result) => {
      let {fileNum, fileList = this.state.fileList, fileSize} = this.props;
      if (fileList.length >= fileNum) throw `最大文件数量为${fileNum}`; //检查文件大小

      let promiseCompress;
      if (fileSize.autoCompress) {
        promiseCompress = Tool.compressImg(result, fileSize.max.size);
      } else {
        promiseCompress = Promise.resolve();
      }

      let data = {file, url: result}; //包装原始的file
      fileList.push(data);
      this.onStart(data, fileList);

      return promiseCompress.then((result2) => {
        if (result2) result2 = Tool.getBlobFormBase64(result2, file.name); //将base64转为file
        data.fileCompress = result2;
        return data;
      });
    });
  };

  /**
   * 上传文件
   * @param file 用户选中的文件
   */
  onUpload = (file) => {
    let {
      action,
      data,
      headers,
      fileList = this.state.fileList,
      onUploadRequest = this.onUploadRequestDefault,
      uploadHandles
    } = this.props;
    let onSuccess = (result) => this.onSuccess(result, file, fileList);
    let onError = (e) => this.onError(e, file, fileList);
    let onUploadProgress = (event) => this.onProgress(event, file, fileList);
    let param = {action, data, headers, file, fileList, onUploadProgress};

    //手动上传
    if (uploadHandles) {
      uploadHandles.push({file, onSuccess, onError, onUploadProgress});
      return file;
    }

    return onUploadRequest(param).then(onSuccess).catch(onError);
  };

  /**
   * 默认的上传文件方法
   * @param param 请求参数，格式：{action,data,headers,file,fileList,onUploadProgress}
   */
  onUploadRequestDefault = (param = {}) => {
    let {action, data, headers, file, fileList, onUploadProgress} = param;
    let dataRequest;
    if (data) {
      dataRequest = {file: file.fileCompress || file.file, ...data};
    } else {
      dataRequest = {file: file.fileCompress || file.file};
    }
    return ToolRequest.ajax(action, dataRequest, {
      headers,
      method: 'POST',
      dataType: 'FORM',
      onUploadProgress
    });
  };

  /**
   * 上传开始的回调
   * @param file 用户选中的文件
   * @param fileList 全部的文件列表
   */
  onStart = (file, fileList) => {
    let {onStart} = this.props;
    file.status = statusList.uploading;
    onStart && onStart(file, fileList);
    this.setState({});
  };

  /**
   * 上传成功的回调
   * @param result 上传成功的结果
   * @param file 用户选中的文件
   * @param fileList 全部的文件列表
   */
  onSuccess = (result, file, fileList) => {
    let {onSuccess} = this.props;
    file.status = statusList.done;
    onSuccess && onSuccess(result, file, fileList);
    this.setState({});
  };

  /**
   * 上传失败的回调
   * @param e 上传失败的结果
   * @param file 用户选中的文件
   * @param fileList 全部的文件列表
   */
  onError = (e, file, fileList) => {
    let {onError} = this.props;
    file.status = statusList.error;
    file.percent = undefined;
    onError && onError(e, file, fileList);
    this.setState({});
  };

  /**
   * 上传进度改变的回调
   * @param event 上传进度事件
   * @param file 用户选中的文件
   * @param fileList 全部的文件列表
   */
  onProgress = (event = {}, file, fileList) => {
    let {onProgress} = this.props;
    let percent = event.loaded / event.total; //上传进度
    file.percent = parseInt(percent * 100);
    onProgress && onProgress(percent, file, fileList);
    this.setState({});
  };

  /**
   * 预览图片的回调
   */
  onPreview = (file, index) => {
    let {fileList = this.state.fileList, onPreview} = this.props;
    onPreview && onPreview(index, fileList);
    this.setState({previewIndex: index});
  };

  /**
   * 删除图片的回调
   */
  onRemove = (file, index) => {
    let {fileList = this.state.fileList, uploadHandles, onRemove} = this.props;
    fileList.splice(index, 1);

    //同步删除手动上传的文件
    if (uploadHandles) {
      let index2 = uploadHandles.findIndex((e) => e.file === file);
      if (index2 >= 0) uploadHandles.splice(index2, 1);
    }

    onRemove && onRemove(index, fileList);
    this.setState({});
  };

  /**
   * 获取图片的类名
   * @param file 文件
   */
  getItemClass(file) {
    let name = '';
    if (file && file.status === statusList.error) {
      name = 'item-error';
    }
    return `item ${name}`;
  }
}
